package sv;

import java.util.ArrayList;

import javax.swing.JOptionPane;

/**
 * Classe che si occupa della soluzione del sudoku
 * 
 * @author Michele&Dario
 * 
 */
public class Solver {
	private static ToolBox tb;
	private OutputWindow oWindow;
	private int nS = 0;
	private boolean[][] choose = new boolean[9][9];

	public Solver(int nS) {
		this.nS = nS;
		tb = new ToolBox();
	}

	public Solver(int nS, int s, boolean test, boolean show) { // costruttore Solver
		this.nS = nS;
		tb = new ToolBox();
		if(show){
			oWindow = new OutputWindow("Solver " + (nS + 1), nS);
			if (nS == 1 && test)
				oWindow.setPosition(343, 23);
			if(!test)
				oWindow.setLocationRelativeTo(null);
		}
		boolean[][][] matrix = tb.getMatrix(nS);
		if(show)
			mostarInFinestra(matrix, oWindow);

		// Primo controllo dei vincoli
		if (controllaVincoli(matrix, nS)) { // se corretti
			if(show)
				mostarInFinestra(matrix, oWindow);
			int rit = ToolBox.getRit();
			if (rit > 0) {
				try {
					synchronized (this) {
						wait(rit);
					}
					;
				} catch (java.lang.InterruptedException ie) {
				}
			}

			int[] index = null;
			if (nS == 0) {
				index = getIndex(matrix, 0, 0, nS);
			}
			if (nS == 1) {
				index = getIndex(matrix, nS, choose);
			}
			// System.out.println(index[0] + " " + index[1]);
			if (index[0] != -1) {
				if (nS == 1)
					setChoose(true, index[0], index[1], choose);
				// dato che ho ricevuto questo indice sono sicuro di avere piu'
				// valori
				int[] values = this.possibleValues(matrix[index[0]][index[1]],
						nS);
				for (int i = 0; i < values.length && !tb.isOver(nS); i++) {
					tb.addPassi(nS);
					if(show)
						oWindow.setCas(index[0], index[1]);
					// crea un nodo per ogni possibile figlio
					boolean[] set = new boolean[] { false, false, false, false,
							false, false, false, false, false };
					set[values[i] - 1] = true;
					boolean[][][] newMatrix = copyMatrix(matrix);// copio la
																	// tabella

					newMatrix[index[0]][index[1]] = set; // e assegno il nuovo
															// valore

					Node node = new Node(newMatrix, choose, tb, oWindow,
							index[0], index[1], nS, show);
				}
				if(show)
					oWindow.setCas(index[0], index[1]);
				if (nS == 1)
					setChoose(false, index[0], index[1], choose);
			} else {
				tb.setOver(nS);
				// TODO finito
			}
			if (tb.isOver(nS)) {
				System.out.println(nS + "--" + s + " Sudoku risolto");
				if (!test)
					JOptionPane.showMessageDialog(null, "Sudoku risolto");

			} else {
				System.out.println(nS + "--" + s + " Sudoku non risolvibile");
				if (!test)
					JOptionPane.showMessageDialog(null,
							"Sudoku non risolvibile");
			}

		} else {
			if(show)
				mostarInFinestra(matrix, oWindow);
			System.out.println(nS + "--" + s + " Sudoku non risolvibile");
			if (!test)
				JOptionPane.showMessageDialog(null, "Sudoku non risolvibile");
		}

		// System.out.println(tb.getPassi(nS)+ " "+ SudokuGames.total());

		int rit = ToolBox.getRit();
		Object wait = ToolBox.getWait();
		if (!tb.isForce() && rit > 0) {
			try {
				synchronized (wait) {
					wait.wait(rit);
				}
			} catch (java.lang.InterruptedException ie) {
			}
		}
		
		if(show)
			oWindow.dispose();
	}

	public static void setChoose(boolean v, int r, int c, boolean[][] choose) {
		choose[r][c] = v;
	}

	/**
	 * Restituisce l'indice (x,y) della prima cella con possibili valori
	 * multipli.
	 * 
	 * @param matrix
	 *            : la matrice corrente
	 * @param x
	 *            : colonna corrente
	 * @param y
	 *            : riga corrente
	 * @return array di due elementi conentente le coordinate x,y
	 */
	public static int[] getIndex(boolean[][][] matrix, int x, int y, int nS) {
		// System.out.println("sono qui "+x+" "+y);
		for (int r = y; r < 9; r++) {
			int ci = x;
			if (r != y)
				ci = 0;
			for (int c = ci; c < 9; c++) {
				int count = countTrue(matrix, r, c, nS);
				if (count > 1) {
					// System.out.println("sono qui "+y+" "+x+" "+count+" "+r+
					// " "+c);
					return new int[] { r, c };
				}
			}
		}
		// TODO il sudoku e' finito
		return new int[] { -1, -1 };
	}

	public static int[] getIndex(boolean[][][] matrix, int nS,
			boolean[][] choose) {
		int ri = 0;
		int co = 0;
		int min = 10;
		// System.out.println("**********************************");
		for (int r = 0; r < 9; r++) {
			for (int c = 0; c < 9; c++) {
				int count = countTrue(matrix, r, c, nS);
				// System.out.println(count+" "+choose[r][c]);
				if (!choose[r][c] && count > 1 && count < min) {
					min = count;
					ri = r;
					co = c;
					// System.out.println(min+" "+ri+" "+co);
				}
			}
		}
		if (min != 10)
			return new int[] { ri, co };
		// TODO il sudoku e' finito
		return new int[] { -1, -1 };
	}

	/**
	 * Metodo che cota il numero dei possibili valori
	 * 
	 * @param matrix
	 *            cubo con valori
	 * @param r
	 *            indice riga
	 * @param c
	 *            indice colonna
	 * @return n° di valori candidati per quella posizione
	 */
	public static int countTrue(boolean[][][] matrix, int r, int c, int nS) {
		int count = 0;
		for (int k = 0; k < 9; k++) {
			tb.addPassi(nS);
			if (matrix[r][c][k])
				count++;
		}
		return count;
	}

	/**
	 * Metodo che indica se c'è un solo valore candidato per una determinata
	 * cella
	 * 
	 * @param matrix
	 *            cubo con valori
	 * @param r
	 *            indice riga
	 * @param c
	 *            indice colonna
	 * @return vero/falso
	 */
	public static boolean hasOneTrue(boolean[][][] matrix, int r, int c, int nS) {
		if (countTrue(matrix, r, c, nS) == 1)
			return true;
		return false;
	}

	/**
	 * Copia la matrice in una nuova
	 * 
	 * @param matrix
	 *            : la matrice da copiare
	 * @return un puntatore ad una nuova matrice
	 */
	public static boolean[][][] copyMatrix(boolean[][][] matrix) {
		boolean[][][] newMatrix = new boolean[9][9][9];
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				newMatrix[i][j] = matrix[i][j].clone();
			}
		}
		return newMatrix;
	}

	/**
	 * Dato un array di boolean restituisce i valori possibili della cella
	 * 
	 * @param values
	 * @return array di valori (<b> non di indici</b>)
	 */
	public static int[] possibleValues(boolean[] values, int nS) {
		int counter = 0;
		for (int i = 0; i < values.length; i++) {
			tb.addPassi(nS);
			if (values[i])
				counter++;
		}
		int[] results = new int[counter];
		counter = 0;
		for (int i = 0; i < values.length; i++) {
			tb.addPassi(nS);
			if (values[i])
				results[counter++] = i + 1;
		}
		return results;
	}

	/**
	 * Metodo che controlla se i vincoli sono soddisfatti o meno
	 * 
	 * @param matrix
	 *            cuboo con valori
	 * @return vero/falso
	 */
	public static boolean controllaVincoli(boolean[][][] matrix, int nS) {
		// int[][] input = tb.getInput();
		for (int r = 0; r < 9; r++)
			for (int c = 0; c < 9; c++) {
				tb.addPassi(nS);
				if (hasOneTrue(matrix, r, c, nS)) {
					int[] val = possibleValues(matrix[r][c], nS);
					if (!tb.applyContstrains(matrix, val[0], r, c, nS)) {
						// System.out.println("Error on [" + r + "] [" + c +
						// "]");
						return false;
					}
				}
			}
		return true;
	}

	/**
	 * Metodo che si occupa della visualizzazione del cubo con i possibili
	 * valori
	 * 
	 * @param matrix
	 */
	public static void mostarInFinestra(boolean[][][] matrix,
			OutputWindow oWindow) {
		for (int r = 0; r < 9; r++)
			for (int c = 0; c < 9; c++) {
				ArrayList<Integer> val = new ArrayList();
				for (int k = 0; k < 9; k++) {
					if (matrix[r][c][k])
						val.add(k + 1);
				}
				oWindow.putElement(r, c, val);
			}
	}
}
